%%% daap_server.erl - a very cheap daap/ http server
%%%
%%% The MIT License
%%%
%%% Copyright (c) 2008 hellomatty@gmail.com
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
%%% of this software and associated documentation files (the "Software"), to deal
%%% in the Software without restriction, including without limitation the rights
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%%% copies of the Software, and to permit persons to whom the Software is
%%% furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in
%%% all copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%%% THE SOFTWARE.
%%%

-module(daap_server).

-import(daap).
-import(dmap).
-import(lists, [reverse/1]).
-include_lib("util.hrl").

-behaviour(gen_server). 

-export([start_link/0]). 
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, 
terminate/2, code_change/3]). 

-define(DAAP_PORT, 36890).
-define(DMAP_MIME, "application/x-dmap-tagged").
-define(STREAM_MIME, "audio/mpeg").

start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 

-record(daap_server_state, {
  listen
}).

init([]) ->  
  {ok, Listen} = gen_tcp:listen(?DAAP_PORT, [binary, {packet, 0}, 
    {reuseaddr, true}, 
    {active, true}]), 
  spawn(fun() -> connect(Listen) end),
  {ok, #daap_server_state{listen = Listen}}. 

handle_call(_Call, _From, State) ->
  {reply, ok, State}.

handle_cast(_Msg, State) -> {noreply, State}. 

handle_info(_Info, State) -> {noreply, State}. 

terminate(_Reason, _State) -> ok. 

code_change(_OldVsn, State, _Extra) -> {ok, State}. 

connect(Listen) ->
  {ok, Socket} = gen_tcp:accept(Listen),
  spawn(fun() -> connect(Listen) end),
  recv_request(Socket, []).

recv_request(Socket, L) ->
  receive
    {tcp, Socket, Bin} ->
      L1 = L ++ binary_to_list(Bin),
      case split(L1, []) of
        more ->
          recv_request(Socket, L1);
        {Request, _Rest} ->
          reply( Socket, generate_response( get_uri( Request ) ) )
      end;
    {tcp_closed, Socket} -> 
      io:format("Server socket closed~n")
  end.

reply(Socket, {stream, Mime}) ->
  Response = lists:flatten([
    "HTTP/1.1 200 OK\r\n",
    lists:flatten(io_lib:format("Content-type: ~s\r\n", [Mime])),
    "\r\n"
  ]),
  gen_tcp:send(Socket, list_to_binary(Response)),
  stream(Socket);
reply(Socket, {send, Mime, ResponseBody}) ->
  Response = lists:flatten([
    "HTTP/1.1 200 OK\r\n",
    lists:flatten(io_lib:format("Content-Length: ~B\r\n", [size(ResponseBody)])),
    lists:flatten(io_lib:format("Content-Type: ~s\r\n", [Mime])),
    "DAAP-Server: bbc2daap-0.1.0\r\n",
    "\r\n",
    ResponseBody
  ]),
  gen_tcp:send(Socket, list_to_binary(Response)),
  recv_request(Socket, []).

stream(Socket) ->
  receive 
    {stream, from, SourcePid, audio, Audio} ->
      case gen_tcp:send(Socket, Audio) of
        {error, Reason} ->
          ?D({daap_server,stream,failed,Reason}),
          exit(SourcePid, normal),
          gen_tcp:close(Socket),
          exit(normal);
        ok ->
          ok
      end,
      stream(Socket);
    stop ->
      gen_tcp:close(Socket)
  end.

generate_response(Uri) ->
  ?D({"Request", Uri}),
  {_Scheme, _Netloc, Path, Query, _Fragment} = urlsplit(Uri),
  case daap:process( string:tokens(Path, "/"), Query ) of
    {stream, start} ->
      {stream, ?STREAM_MIME};
    {send, {dmap, Dmap}} ->
%     ?D({daap_server,send,dmap,Dmap}),
      {send, ?DMAP_MIME, dmap:encode( Dmap )}
  end.

get_uri(Request) ->
  [H|_Rest] = string:tokens(Request, "\r\n"),
  [_Method, Uri, _Proto] = string:tokens(H, " "),
  Uri.

split("\r\n\r\n" ++ T, L) -> {reverse(L), T}; 
split([H|T], L) -> split(T, [H|L]); 
split([], _) -> more. 

urlsplit(Url) ->
    {Scheme, Url1} = urlsplit_scheme(Url),
    {Netloc, Url2} = urlsplit_netloc(Url1),
    {Path, Query, Fragment} = urlsplit_path(Url2),
    {Scheme, Netloc, Path, Query, Fragment}.

urlsplit_scheme(Url) ->
    urlsplit_scheme(Url, []).

urlsplit_scheme([], Acc) ->
    {"", lists:reverse(Acc)};
urlsplit_scheme(":" ++ Rest, Acc) ->
    {string:to_lower(lists:reverse(Acc)), Rest};
urlsplit_scheme([C | Rest], Acc) ->
    urlsplit_scheme(Rest, [C | Acc]).

urlsplit_netloc("//" ++ Rest) ->
    urlsplit_netloc(Rest, []);
urlsplit_netloc(Path) ->
    {"", Path}.

urlsplit_netloc(Rest=[C | _], Acc) when C =:= $/; C =:= $?; C =:= $# ->
    {lists:reverse(Acc), Rest};
urlsplit_netloc([C | Rest], Acc) ->
    urlsplit_netloc(Rest, [C | Acc]).


%% @spec urlsplit_path(Url) -> {Path, Query, Fragment}
%% @doc Return a 3-tuple, does not expand % escapes. Only supports HTTP style
%%      paths.
urlsplit_path(Path) ->
    urlsplit_path(Path, []).

urlsplit_path("", Acc) ->
    {lists:reverse(Acc), "", ""};
urlsplit_path("?" ++ Rest, Acc) ->
    {Query, Fragment} = urlsplit_query(Rest),
    {lists:reverse(Acc), Query, Fragment};
urlsplit_path("#" ++ Rest, Acc) ->
    {lists:reverse(Acc), "", Rest};
urlsplit_path([C | Rest], Acc) ->
    urlsplit_path(Rest, [C | Acc]).

urlsplit_query(Query) ->
    urlsplit_query(Query, []).

urlsplit_query("", Acc) ->
    {lists:reverse(Acc), ""};
urlsplit_query("#" ++ Rest, Acc) ->
    {lists:reverse(Acc), Rest};
urlsplit_query([C | Rest], Acc) ->
    urlsplit_query(Rest, [C | Acc]).
